use super::{
    err, err_value, get_cfg, json, log_pack, ok,
    pbase::{PairBase, SData},
    save_cfg, save_data, throw_err, Base, Client, ECode, Lock, MapSv, RNull, Resp, TBase, VBase,
    VMap, Value, VecV, SS,
};
use std::collections::{HashMap, HashSet};
use tracing::{instrument, warn};

pub struct Auto;

impl Base for Auto {}
impl TBase for Auto {}

impl Auto {
    pub async fn do_cmd(&self, para: &Value, cmd: &str, client: &Client, lock: Lock) -> Resp {
        let data = para.ckobj()?;
        let line = data.ck_str("line")?;

        match cmd {
            "clear" => self.do_clear(line, data, client, lock).await,
            "pair" => {
                let tp = data.ck_str("tp")?;
                let psn = data.ck_str("psn")?;

                let ret = self.do_pair(line, tp, psn, client, lock).await;

                if let Err(_) = ret {
                    let cmd = json!({
                        "line": line,
                        "tp": tp,
                        "sn": psn,
                    });
                    let _ = client.job(&cmd, "pick/add").await;
                }

                ret
            }
            "restore" => self.do_restore(line, data, client, lock).await,
            "start" => self.do_start(line, data, client, lock).await,
            "load" => self.do_load(line, client).await,
            _ => throw_err!(ECode::NoServe, "no auto serve"),
        }
    }

    #[instrument(skip_all, name = "do_load")]
    async fn do_load(&self, line: &str, client: &Client) -> Resp {
        let cmd = json!({ "line": line });
        let mut body = client.cfgdb(&cmd, "field/list/autopair").await?;
        let body = body.ck_obj_mut("data")?;

        let mt = body.ck_str("mt")?;
        let mt_mod = self.get_mt_type(mt)?;

        let batch = body.ck_array("batch")?;

        let mut ret = Vec::new();

        for d in batch.iter() {
            let mut d = d.ckobj()?.clone();
            let sn = d.ck_str("sn")?;

            let cmd = json!({
                "line": line,
                "k": sn,
            });

            if let Ok(body_mt) = client.cfgdb(&cmd, &format!("{mt_mod}/get")).await {
                let num = body_mt.ck_obj("data")?.ck_i64("num")?;
                d.insert("num".to_string(), json!(num));
            }

            ret.push(d);
        }

        body.insert("batch".to_string(), json!(ret));

        ok!(body)
    }

    #[instrument(skip_all, name = "do_clear")]
    async fn do_clear(&self, line: &str, data: &MapSv, client: &Client, lock: Lock) -> Resp {
        let _ = lock.lock().await;

        let psn = data.ck_str("psn")?;
        let tp = data.ck_str("tp")?;

        let fname = match tp {
            "inner" => "autoin",
            "outer" => "autout",
            _ => throw_err!(ECode::Para, "type error"),
        };

        let cmd = json!({ "line": line });
        let body = client.cfgdb(&cmd, "field/list/autopair").await?;
        let body = body.ck_obj("data")?;
        let tsn = body.ck_str("tsn")?;

        let cmd = json!({
            "line": line,
            "sn": psn,
        });

        let _ = client
            .cfgdb(&cmd, &format!("file/rm/{fname}_{tsn}"))
            .await?;

        ok!()
    }

    #[instrument(skip_all, name = "do_restore")]
    async fn do_restore(&self, line: &str, data: &MapSv, client: &Client, lock: Lock) -> Resp {
        let _ = lock.lock().await;

        let box_sn = data.ck_str("k")?;
        let box_tp = data.ck_str("tp")?;

        let tab = match box_tp {
            "inner" => "autoin",
            "outer" => "autout",
            _ => throw_err!(ECode::Para, "box type error"),
        };

        let cmd = json!({ "line": line });

        let box_body = client
            .cfgdb(&cmd, &format!("file/list/box_{box_sn}"))
            .await?;
        let box_body = box_body.ck_array("data")?;

        let mut psn_set = HashSet::new();

        for d in box_body.iter() {
            let sn = d.ck_str("sn")?;
            if sn == "attr" {
                continue;
            }

            psn_set.insert(sn);
        }

        warn!("psn_set len: {:?}", psn_set.len());

        let mut outer = MapSv::new();
        let mut inner = MapSv::new();

        for psn in psn_set.iter() {
            let cmd = json!({
                "n": tab,
                "obj": "*",
                "opt": {
                    "desc": true,
                    "count": 1,
                    "and": [
                    {"eq": "line", "v": line},
                    {"eq": "psn", "v": psn},
                    ]
                }
            });

            let body = match client.timedb(&cmd, "get").await {
                Ok(v) => v,
                Err(_) => {
                    continue;
                }
            };

            let data = body.ck_obj("data")?.ck_obj(tab)?.ck_array("data")?;

            let _ = self.collect(&mut outer, &mut inner, tab, &data);
        }

        if let Err(e) = save_data(line, "autout", &outer, client).await {
            warn!("save autout error: {:?}", e);
        }

        if let Err(e) = save_data(line, "autoin", &inner, client).await {
            warn!("save autoin error: {:?}", e);
        }

        warn!(
            "autout len: {:?}, autoin len: {:?}",
            outer.len(),
            inner.len()
        );

        ok!(json!({
            "outer": outer,
            "inner": inner,
        }))
    }

    async fn do_start(&self, line: &str, data: &MapSv, client: &Client, lock: Lock) -> Resp {
        let _ = lock.lock().await;

        let tsn = data.ck_str("k")?;

        warn!("step: load data");

        let _ = self.do_data(line, client).await;

        let cmd = json!({ "line": line });

        let _ = client.cfgdb(&cmd, "file/rmfile/autout").await?;
        let _ = client.cfgdb(&cmd, "file/rmfile/autoin").await?;

        let _ = client
            .cfgdb(&cmd, &format!("file/rmfile/autout_{tsn}"))
            .await?;
        let _ = client
            .cfgdb(&cmd, &format!("file/rmfile/autoin_{tsn}"))
            .await?;

        ok!()
    }

    async fn add_task(&self, line: &str, tsn: &str, data: &Value, client: &Client) -> RNull {
        let now = self.cur_time("%Y%m%d");
        let task_sn = format!("auto_{tsn}_{now}");

        let cmd = json!({
            "line": line,
            "k": &task_sn,
        });

        if let Err(_) = client.cfgdb(&cmd, "task/get").await {
            let cmd = json!({
                "line": line,
                "v": {
                    "sn": task_sn,
                    "pstart": base::cur_time("%Y-%m-%d %H:%M:%S"),
                    "pend": self.next_cur_time_day(1, "%Y-%m-%d %H:%M:%S")?,
                    "pnum": 20000,
                    "tech": tsn,
                    "state": "open",
                }
            });

            client.cfgdb(&cmd, "task/add").await?;
        }

        let cmd = json!({
            "line": "1",
            "k": task_sn,
            "v": [data],
        });

        client.cfgdb(&cmd, "psn/add").await?;

        Ok(())
    }

    async fn do_pair(
        &self,
        line: &str,
        find_tp: &str,
        find_psn: &str,
        client: &Client,
        lock: Lock,
    ) -> Resp {
        let _ = lock.lock().await;

        let cmd = json!({ "line": line });

        let mut body = client.cfgdb(&cmd, "field/list/autopair").await?;
        let body = body.ck_obj_mut("data")?;

        body.insert("gc".to_string(), json!(true));

        let ball_num = body.ck_i64("num")?;
        let ball_size = body.ck_f64("ball")?;
        let mt = body.ck_str("mt")?;

        let mut pbase = PairBase::new();

        pbase.para(line, &body)?;
        pbase.get_tech(client, false).await?;

        let _ = self.do_data(line, client).await;

        pbase.load(client, "autout", "autoin").await?;

        let ball_body = body.ck_array("batch")?;

        let mut ball = Vec::new();

        for d in ball_body.iter() {
            let d = d.ckobj()?;

            let sn = d.ck_str("sn")?;
            let dw = d.ck_f64("dw")?;
            let std = d.ck_f64("std")?;
            let sub = d.ck_f64("sub")?;

            if let Err(_) = pbase.ck_ball_num(sn, client).await {
                continue;
            }

            ball.push((sn.to_string(), dw, std, sub));
        }

        let mut find_in = "".to_string();
        let mut find_out = "".to_string();
        let mut out_val = json!({});
        let mut dw_val = 0.0;

        warn!("step: start pair");

        let tsn = pbase.tsn();
        let mut all_log = Vec::new();

        for (out_psn, out_data) in pbase.out_tech.iter() {
            let out = match self.get_pair_val(out_data) {
                Ok(v) => v,
                Err(_) => {
                    continue;
                }
            };

            if !pbase.ck_outdia(&out, &mut all_log) {
                continue;
            }

            let mut log = HashMap::new();

            for (in_psn, in_data) in pbase.in_tech.iter() {
                let inval = match self.get_pair_val(in_data) {
                    Ok(v) => v,
                    Err(_) => {
                        continue;
                    }
                };

                if !pbase.ck_india(&inval, &mut log) {
                    continue;
                }

                if !pbase.ck_width(&out, &inval, &mut log) {
                    continue;
                }

                if !pbase.ck_dia(&out, &inval, &mut log) {
                    continue;
                }

                let ball_batch = match pbase.ck_ball(&ball, &out, &inval, &mut log) {
                    Some(v) => v,
                    None => {
                        continue;
                    }
                };

                match find_tp {
                    "outer" => {
                        if find_psn != out_psn {
                            continue;
                        }
                    }
                    "inner" => {
                        if find_psn != in_psn {
                            continue;
                        }
                    }
                    _ => {
                        continue;
                    }
                }

                let f = &ball_batch[0];

                find_in = in_psn.to_string();
                find_out = out_psn.to_string();
                dw_val = f.ck_f64("dw")? / 1000.0;

                out_val = json!({
                    "sn": find_out,
                    "insn": find_in,
                    "outdia": self.to_m(out.dia),
                    "outdt": self.to_m(out.dt),
                    "outwidth": self.to_m(out.width),
                    "india": self.to_m(inval.dia),
                    "indt": self.to_m(inval.dt),
                    "inwidth": self.to_m(inval.width),
                    "bsn": f.ck_str("sn")?,
                    "ball": ball_size,
                    "num": ball_num,
                    "std": f.ck_f64("std")?,
                    "sub": f.ck_f64("sub")?,
                    "mt": mt,
                    "dw": self.to_m(out.dt - inval.dt - 2.0 * dw_val),
                    "tech": tsn,
                    "boxpack": "",
                });

                log.clear();
                break;
            } //for

            if !log.is_empty() {
                for (_, v) in log.iter() {
                    all_log.push(v.clone());
                }
            }

            if !find_out.is_empty() {
                break;
            }
        }

        if find_out.is_empty() {
            for d in all_log.iter() {
                warn!("{:?}", d);
            }

            throw_err!(ECode::EmptyData, "no data");
        }

        warn!("step: rm from autout_{tsn}");

        let cmd = json!({
            "line": line,
            "sn": find_out,
        });
        let _ = client.cfgdb(&cmd, &format!("file/rm/autout_{tsn}")).await?;

        warn!("step: rm from autoin_{tsn}");

        let cmd = json!({
            "line": line,
            "sn": find_in,
        });
        let _ = client.cfgdb(&cmd, &format!("file/rm/autoin_{tsn}")).await?;

        self.add_task(line, tsn, &out_val, client).await?;

        let cmd = json!({ "line": line, "k": find_in });
        let _ = client.cfgdb(&cmd, "inner/rm").await;

        let cmd = json!({ "line": line, "k": find_out });
        let _ = client.cfgdb(&cmd, "outer/rm").await;

        let _ = log_pack(line, "apairlog", &find_out, tsn, client).await;

        ok!(json!({
            "outer": find_out,
            "inner": find_in,
            "dw": dw_val,
            "val": out_val,
        }))
    }

    /* tsn: {
     *    psn: {
     *       "inwidth": xx,
     *       ...
     *    }
     * }
     */
    #[instrument(skip_all, name = "do_data")]
    async fn do_data(&self, line: &str, client: &Client) -> RNull {
        warn!("step: load data");

        let mut outer = MapSv::new();
        let mut inner = MapSv::new();

        if let Err(e) = self.load_data(line, &mut outer, &mut inner, client).await {
            warn!("load data error: {:?}", e);
        }

        if let Err(e) = save_data(line, "autout", &outer, client).await {
            warn!("save autout error: {:?}", e);
        }

        if let Err(e) = save_data(line, "autoin", &inner, client).await {
            warn!("save autoin error: {:?}", e);
        }

        Ok(())
    }

    async fn load_data(
        &self,
        line: &str,
        outer: &mut MapSv,
        inner: &mut MapSv,
        client: &Client,
    ) -> RNull {
        let mut cfg = SS::new();

        let _ = get_cfg("autopos", line, &mut cfg, client).await;

        let tab = vec!["autoin", "autout"];

        for d in tab.iter() {
            if let Ok(body) = self.read_data(line, d, &cfg, client).await {
                let body = body.ck_obj("data")?.ck_obj(d)?;

                let end = body.ck_str("end")?;
                let data = body.ck_array("data")?;

                cfg.insert(d.to_string(), end.to_string());

                let _ = self.collect(outer, inner, d, &data);
            }
        }

        let _ = save_cfg("autopos", line, &cfg, client).await;

        Ok(())
    }

    fn collect(
        &self,
        outer: &mut MapSv,
        inner: &mut MapSv,
        tab: &str,
        data: &VecV,
    ) -> Result<(), Value> {
        for d in data.iter() {
            let psn = d.ck_str("psn")?;
            let tsn = d.ck_str("tsn")?;
            let width = d.ck_f64("width")?;
            let dia = d.ck_f64("dia")?;
            let dt = d.ck_f64("dt")?;

            let val = json!({
                "width": width,
                "dia": dia,
                "dt": dt,
            });

            if tab.ends_with("in") {
                self.insert_val(inner, tsn, psn, val);
            } else if tab.ends_with("out") {
                self.insert_val(outer, tsn, psn, val);
            }
        }

        Ok(())
    }

    /* tsn: {
     *    psn: {
     *       "width": xx,
     *       ...
     *    }
     * }
     */
    fn insert_val(&self, ret: &mut MapSv, tsn: &str, psn: &str, val: Value) {
        match ret.k_obj_mut(tsn) {
            Some(v) => {
                v.insert(psn.to_string(), val);
            }
            None => {
                ret.insert(
                    tsn.to_string(),
                    json!({
                        psn: val
                    }),
                );
            }
        }
    }

    async fn read_data(&self, line: &str, tab: &str, cfg: &SS, client: &Client) -> Resp {
        // 2 second
        let now = self.pre_cur_time(1000 * 2, "%Y-%m-%dT%H:%M:%SZ")?;
        let utc = self.utc_offset_from_zero();

        let cmd = if let Some(t) = cfg.k_str(tab) {
            json!({
                "n": tab,
                "obj": "*",
                "opt": {
                    "and": [
                    {"eq": "line", "v": "1"},
                    {"gt": "time", "v": t},
                    {"le": "time", "v": now, "utc": utc},
                    ]
                }
            })
        } else {
            let t = self.pre_cur_time(1000 * 60 * 60 * 8, "%Y-%m-%dT%H:%M:%SZ")?;
            json!({
                "n": tab,
                "obj": "*",
                "opt": {
                    "and": [
                    {"eq": "line", "v": line},
                    {"gt": "time", "v": t, "utc": utc},
                    {"le": "time", "v": now, "utc": utc},
                    ]
                }
            })
        };

        client.timedb(&cmd, "get").await
    }

    fn get_pair_val(&self, data: &Value) -> Result<SData, Value> {
        let data = data.ckobj()?;

        let (dia, dt, width) = self.get_val(data)?;

        let out = SData::new(
            0.0,
            0.0,
            0.0,
            dia,
            dt,
            width,
            "".to_string(),
            "".to_string(),
        );

        Ok(out)
    }

    fn get_val(&self, data: &MapSv) -> Result<(f64, f64, f64), Value> {
        let dia = data.ck_f64("dia")?; // D
        let dt = data.ck_f64("dt")?; // De
        let width = data.ck_f64("width")?; // C

        Ok((self.round_dia(dia / 1000.0), dt / 1000.0, width / 1000.0))
    }
}
